<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<script>

  /**
   * Automatically extend using objects referenced in `behaviors` array.
   *
   *     someBehaviorObject = {
   *       accessors: {
   *        value: {type: Number, observer: '_numberChanged'}
   *       },
   *       observers: [
   *         // ...
   *       ],
   *       ready: function() {
   *         // called before prototoype's ready
   *       },
   *       _numberChanged: function() {}
   *     };
   *
   *     Polymer({
   *
   *       behaviors: [
   *         someBehaviorObject
   *       ]
   *
   *       ...
   *
   *     });
   *
   * @class base feature: behaviors
   */

  Polymer.Base._addFeature({

    /**
     * Array of objects to extend this prototype with.
     *
     * Each entry in the array may specify either a behavior object or array
     * of behaviors.
     *
     * Each behavior object may define lifecycle callbacks, `properties`,
     * `hostAttributes`, `observers` and `listeners`.
     *
     * Lifecycle callbacks will be called for each behavior in the order given
     * in the `behaviors` array, followed by the callback on the prototype.
     * Additionally, any non-lifecycle functions on the behavior object are
     * mixed into the base prototype, such that same-named functions on the
     * prototype take precedence, followed by later behaviors over earlier
     * behaviors.
     */
    behaviors: [],

    _prepBehaviors: function() {
      if (this.behaviors.length) {
        this.behaviors = this._flattenBehaviorsList(this.behaviors);
      }
      this._prepAllBehaviors(this.behaviors);
    },

    _flattenBehaviorsList: function(behaviors) {
      var flat = [];
      behaviors.forEach(function(b) {
        if (b instanceof Array) {
          flat = flat.concat(this._flattenBehaviorsList(b));
        }
        // filter out null entries so other iterators don't need to check
        else if (b) {
          flat.push(b);
        } else {
          this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for missing or 404 import'));
        }
      }, this);
      return flat;
    },

    _prepAllBehaviors: function(behaviors) {
      // traverse the behaviors in _reverse_ order (youngest first) because
      // `_mixinBehavior` has _first property wins_ behavior, this is done
      // to optimize # of calls to `_copyOwnProperty`
      for (var i=behaviors.length-1; i>=0; i--) {
        this._mixinBehavior(behaviors[i]);
      }
      // we iterate a second time so that `_prepBehavior` goes in natural order
      // otherwise, it's a tricky detail for implementors of `_prepBehavior`
      for (var i=0, l=behaviors.length; i<l; i++) {
        this._prepBehavior(behaviors[i]);
      }
      // prep our prototype-as-behavior
      this._prepBehavior(this);
    },

    _mixinBehavior: function(b) {
       Object.getOwnPropertyNames(b).forEach(function(n) {
        switch (n) {
          case 'hostAttributes':
          case 'registered':
          case 'properties':
          case 'observers':
          case 'listeners':
          case 'created':
          case 'attached':
          case 'detached':
          case 'attributeChanged':
          case 'configure':
          case 'ready':
            break;
          default:
            if (!this.hasOwnProperty(n)) {
              this.copyOwnProperty(n, b, this);
            }
            break;
        }
      }, this);
    },

    _doBehavior: function(name, args) {
      this.behaviors.forEach(function(b) {
        this._invokeBehavior(b, name, args);
      }, this);
      this._invokeBehavior(this, name, args);
    },

    _invokeBehavior: function(b, name, args) {
      var fn = b[name];
      if (fn) {
        fn.apply(this, args || Polymer.nar);
      }
    },

    _marshalBehaviors: function() {
      this.behaviors.forEach(function(b) {
        this._marshalBehavior(b);
      }, this);
      this._marshalBehavior(this);
    }

  });

</script>
